package com.jinzhi.wxpay.service.impl;


import com.google.gson.Gson;
import com.jinzhi.jzapi.domain.UserDO;
import com.jinzhi.jzapi.utils.JWTUtil;
import com.jinzhi.jzweb.domain.JazUserIntegralDO;
import com.jinzhi.jzweb.service.JazUserIntegralService;
import com.jinzhi.sys.service.UserService;
import com.jinzhi.wxpay.config.WxPayConfigZF;
import com.jinzhi.wxpay.entity.OrderInfo;
import com.jinzhi.wxpay.enums.OrderStatus;
import com.jinzhi.wxpay.enums.wxpay.WxApiType;
import com.jinzhi.wxpay.enums.wxpay.WxNotifyType;
import com.jinzhi.wxpay.service.OrderInfoService;
import com.jinzhi.wxpay.service.WxPayService;
import com.jinzhi.wxpay.util.GetOpenIdUtil;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;


//当前接口的实现类
@Slf4j
@Service
public class WxPayServiceImpl implements WxPayService {


    @Resource
    private WxPayConfigZF wxPayConfigZF;

    //因为在方法上面使用了Bean标签，spring会自动把方法实例化，因此可以直接将方法引入进来
    @Resource
    private CloseableHttpClient  wxPayClient;

    @Resource
    private OrderInfoService orderInfoService;

    @Autowired
    private UserService userService;

    @Autowired
    private JazUserIntegralService jazUserIntegralService; //积分明细表


    private  final ReentrantLock lock = new ReentrantLock();


    /**JF
     * 创建订单，调用小程序支付接口（进入订单处理）
     * @param  title：商品名 ,totalFee：积分金额，userId：用户id
     * @return  prepay_id 和  订单号
     * @throws IOException
     */
    @Override
    public Map<String,Object> JsApinativePay(String title, String openid, BigDecimal totalFee) throws Exception {
//        totalFee = totalFee.divide(new BigDecimal(100));
        UserDO user = JWTUtil.getUser();
        //测试一分钱支付
//        BigDecimal totalFeeCS = new BigDecimal(0.01);
        BigDecimal totalFeeCS = BigDecimal.valueOf(1).setScale(2,BigDecimal.ROUND_HALF_UP);
        //生成订单
        log.info("生成订单");
        OrderInfo orderByProductId = orderInfoService.createOrderByJF(title,user.getId(),totalFee);
        //微信登录授权获取openid
//        String openid = GetOpenIdUtil.decodeUserInfo(wxPayConfig.getAppid(), code, wxPayConfig.getApiV3Key());
        //调用统一下单api
        log.info("调用统一下单API");
        //调用统一下单API
        HttpPost httpPost = new HttpPost(wxPayConfigZF.getDomain().concat(WxApiType.JSAPI_PAY.getType()));
        // 请求body参数
        Gson gson = new Gson();
        Map paramsMap = new HashMap();
        log.info("appid========>"+ wxPayConfigZF.getAppid());
        paramsMap.put("appid", wxPayConfigZF.getAppid());
        paramsMap.put("mchid", wxPayConfigZF.getMchId());
        paramsMap.put("description", title);
        paramsMap.put("out_trade_no", orderByProductId.getOrderNo());//为自动生成
        paramsMap.put("notify_url", wxPayConfigZF.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));//支付通知接口（支付成功后自动调用这个接口）

        Map amountMap = new HashMap();
        Map payerMap = new HashMap();
        BigDecimal d = new BigDecimal(100);//微信支付算的是分，因此这里需要乘以100
        BigDecimal fee =  d.multiply(totalFee);
        amountMap.put("total", fee);//总金额
        amountMap.put("currency", "CNY");//货币类型
        payerMap.put("openid",openid);
        paramsMap.put("amount", amountMap);
        paramsMap.put("payer", payerMap);
        //将参数转为json字符串
        String jsonParams = gson.toJson(paramsMap);
        log.info("请求参数 ===> {}" + jsonParams);

        //将请求参数封装进Entity对象中
        StringEntity entity = new StringEntity(jsonParams,"utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
        log.info("将请求参数封装进Entity对象中===>"+httpPost);
        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpPost);
        log.info("获取用户私钥成功=======》"+response);
        //对请求结果的处理
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity()); //响应体
            log.info("响应体======》"+response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode(); //响应状态码
            log.info("响应状态码："+statusCode);
            if (statusCode == 200) { //处理成功
                log.info("成功, 返回结果 = " + bodyAsString);
            } else if (statusCode == 204) { //处理成功，无返回Body
                log.info("成功");
            } else {
                log.info("小程序 下单失败，响应码 " + statusCode+ ",返回结果 = " + bodyAsString);
                throw new IOException("request failed");
            }
            //响应结果
            HashMap<String,String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
            log.info("响应结果=====>"+resultMap);

            //返回所需的参数
            Map<String, Object> map = new HashMap<>();
            map.put("total_fee", d.multiply(totalFee));
            //返回小程序调起微信支付所需要的参数
            //小程序id
            map.put("appid", wxPayConfigZF.getAppid());
            //时间戳
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");//得到一个时间戳
            String  newDate = sdf.format(new Date());
            map.put("timeStamp", newDate);
            //随机字符串
            Random random = new Random();//得到一个随机数字符串
            String nonceStr = "";
            for (int i = 0; i < 3; i++){
                nonceStr += random.nextInt(10);//十位数字的随机字符串
            }
            map.put("nonceStr",nonceStr);
            //订单详情扩展字符串
            map.put("package","prepay_id="+resultMap.get("prepay_id"));
            //签名方式
            map.put("signType","RSA");
            //签名(需要算法计算)
           String paySign = GetOpenIdUtil.getSign(wxPayConfigZF.getAppid(),Long.parseLong(newDate),nonceStr,"prepay_id="+resultMap.get("prepay_id"));
           map.put("paySign",paySign);
           return map;
        } finally {
            response.close();
        }

    }




    /**
     * 创建订单，调用Native支付接口（进入订单处理）
     * @param productId  商品id
     * @return  code_url 和  订单号
     * @throws IOException
     */
    @Override
    public Map<String,Object> nativePay(Long userId,Long  productId) throws Exception {
        OrderInfo orderInfo = new OrderInfo();
        //生成订单
        log.info("生成订单");
        OrderInfo orderByProductId = orderInfoService.createOrderByProductId(userId,productId);

        //二维码地址
        String codeUrl = orderByProductId.getCodeUrl();
        //判断二维码订单是否存在
        if (!StringUtils.isEmpty(codeUrl)){
            log.info("二维码订单已经存在");
            //返回二维码
            Map<String, Object> map = new HashMap<>();
            map.put("codeUrl",codeUrl);
            map.put("orderNo",orderByProductId.getOrderNo());
            return map;
        }
        //将订单信息存入数据库，包括用户信息

        //调用统一下单api
        log.info("调用统一下单API");
        //调用统一下单API
        HttpPost httpPost = new HttpPost(wxPayConfigZF.getDomain().concat(WxApiType.NAITVE_PAY.getType()));
        // 请求body参数
        Gson gson = new Gson();
        Map paramsMap = new HashMap();
        log.info("appid========>"+ wxPayConfigZF.getAppid());
        paramsMap.put("appid", wxPayConfigZF.getAppid());
        paramsMap.put("mchid", wxPayConfigZF.getMchId());
        paramsMap.put("description", orderByProductId.getTitle());
        paramsMap.put("out_trade_no", orderByProductId.getOrderNo());
        paramsMap.put("notify_url", wxPayConfigZF.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));//支付通知接口（支付成功后自动调用这个接口）

        Map amountMap = new HashMap();
        amountMap.put("total", orderByProductId.getTotalFee());//总金额
        amountMap.put("currency", "CNY");//货币类型

        paramsMap.put("amount", amountMap);
        //将参数转为json字符串
        String jsonParams = gson.toJson(paramsMap);
        log.info("请求参数 ===> {}" + jsonParams);

        //将请求参数封装进Entity对象中
        StringEntity entity = new StringEntity(jsonParams,"utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
        log.info("将请求参数封装进Entity对象中===>"+httpPost);
        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpPost);
        log.info("获取用户私钥成功=======》"+response);
        //对请求结果的处理
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity()); //响应体
            log.info("响应体======》"+response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode(); //响应状态码
            log.info("响应状态码："+statusCode);
            if (statusCode == 200) { //处理成功
                log.info("成功, 返回结果 = " + bodyAsString);
            } else if (statusCode == 204) { //处理成功，无返回Body
                log.info("成功");
            } else {
                log.info("Native 下单失败，响应码 " + statusCode+ ",返回结果 = " + bodyAsString);
                throw new IOException("request failed");
            }
            //响应结果
            HashMap<String,String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
            log.info("响应结果=====>"+resultMap);
            //二维码
            codeUrl = resultMap.get("code_url");
            //保存二维码
            String orderNo = orderInfo.getOrderNo();
            orderInfoService.saveCodeUrl(orderNo,codeUrl);


            //返回二维码
            Map<String, Object> map = new HashMap<>();
            map.put("codeUrl",codeUrl);
            map.put("orderNo",orderByProductId.getOrderNo());
            return map;
        } finally {
            response.close();
        }

    }

    /**
     * 支付成功后处理数据库里的订单支付状态
     * */
    @Override
    public void processOrder(Map<String, Object> bodyMap) throws GeneralSecurityException {
        log.info("处理订单");
        //解密报文
        String plainText = decryptFromResource(bodyMap);
        //将明文转换成map
        Gson gson = new Gson();
        HashMap  plainTextMap = gson.fromJson(plainText,HashMap.class);//将报文转换为json字符串
        String orderNo = (String) plainTextMap.get("out_trade_no");//订单号
        /**
         * 在对业务数据进行状态检查和处理前，要采用数据锁惊进行并发控制
         * 以免函数重入造成数据混乱
         * ：支付过程中可能出现两个人同时支付，返回数据同时出现，后面的更改订单状态也会同时更改这样就会出现数据混乱的问题
         * ：这里就要做一个高并发的水处理
         * */
        //获取锁，成功获取则立即返回true，获取失败返回false。这里的锁是如果获取失败就立即退出了，不会进行等待，不必等待锁的释放（会先跳出然后执行后面锁的释放）（与分布式事务锁不同(通常可以使用Redis或mq的分布式事务锁，这里的锁在分布式项目中锁不住)）
        if (lock.tryLock()){//加个锁，做一个并发处理
            try{

                //处理重复的通知（根据订单号查询订单状态（已支付，未支付）），如果支付过程中出现了问题(如网络问题，出现了支付中断)，那么微信支付会一直发送支付通知，这里需要处理一下
                //接口调用的幂等性：无论接口被调用多少次，产生的结果是一致的。（）
                OrderInfo orderStatus = orderInfoService.getOrderStatus(orderNo);//查询订单的支付状态
                if (!OrderStatus.NOT_PAY.getType().equals(orderStatus.getOrderStatus())){//如果不是未支付（NOT_PAY为未支付）
                    return;//支付状态如果已未支付，那个就直接return，只有订单是未支付才能更改支付状态
                }
                //更新数据库中订单状态
                orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
                //
                UserDO userDO = userService.selectById(orderStatus.getUserId());//用户信息
                //给用户加积分
                UserDO userDO1 = new UserDO();
                userDO1.setId(userDO.getId());
                userDO1.setCurrentPoints(String.valueOf(Double.valueOf(userDO.getCurrentPoints()) + Double.valueOf(orderStatus.getTitle().replace("积分",""))));//当前积分(购买成功后给当前用户加积分)
                userDO1.setAccumulatedPoints(String.valueOf(Double.valueOf(userDO.getAccumulatedPoints()) + Double.valueOf(orderStatus.getTitle().replace("积分",""))));//累计积分
                userService.updateById(userDO1);
                //更改积分记录数据
                JazUserIntegralDO zdUserIntegralDO = new JazUserIntegralDO();
                zdUserIntegralDO.setIntegralDetail("+"+orderStatus.getTitle().replace("积分","")); //积分
                zdUserIntegralDO.setSysUserid(userDO.getId()); //用户id
                zdUserIntegralDO.setIntegralTime(new Date()); //时间
                zdUserIntegralDO.setIntegralWay("积分充值"); //用途
                zdUserIntegralDO.setCurrentIntegral(userDO.getCurrentPoints()); //当前积分
                jazUserIntegralService.insert(zdUserIntegralDO);
                //记录支付日志(待定)

            }finally {
                //主动释放锁（使用了锁之后必须要主动释放锁）
                lock.unlock();
            }


        }

    }

    /**
     * 对称解密
     *
     * @return*/
    private String decryptFromResource(Map<String, Object> bodyMap) throws GeneralSecurityException {
        log.info("解密");
        //通知数据
        Map<String,String> resourceMap = (Map) bodyMap.get("resource");
        //获取数据秘文
        String ciphertext = resourceMap.get("ciphertext");
        //获取随机串
        String nonce = resourceMap.get("nonce");
        //获取附加数据
        String associatedData = resourceMap.get("associated_data");


        AesUtil aesUtil = new AesUtil(wxPayConfigZF.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
                nonce.getBytes(StandardCharsets.UTF_8),ciphertext);
        log.info("明文=====》 {}",plainText);
        /**
         *
         * 包括:mchid(商户号),appid(微信id),transaction_id(微信生成的唯一订单号)
         * */
        log.info("秘文=====》 {}",ciphertext);
        return plainText;
    }

}
